use crate::action::record::{
    get_daily_geoip, get_month_geoip, get_week_geoip, get_year_geoip, GeoIpStats,
};
use crate::data::message::Message;
use crate::model::date::{Day, Month, Week, Year};
use crate::{fairing::record::RecordDb, model::user::UserData};
use rocket::http::Status;
use rocket::request::Request;
use rocket::response::{self, Responder, Response};
use rocket::route::Route;
use rocket::serde::json::Json;
use rocket_db_pools::{
    sqlx::{self, Acquire},
    Connection,
};

enum GeoipResult {
    Ok(GeoIpStats),
    Err(sqlx::Error),
}

impl<'r> Responder<'r, 'static> for GeoipResult {
    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
        match self {
            GeoipResult::Ok(entries) => Response::build_from(Json(entries).respond_to(req)?)
                .status(Status::Ok)
                .ok(),
            GeoipResult::Err(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
        }
    }
}

impl From<sqlx::Error> for GeoipResult {
    fn from(err: sqlx::Error) -> Self {
        GeoipResult::Err(err)
    }
}

#[get("/geoip?<year>", rank = 4)]
async fn geoip_yearly(mut db: Connection<RecordDb>, _user: UserData, year: Year) -> GeoipResult {
    let result = async {
        let mut tx = db.begin().await?;
        Ok(get_year_geoip(&mut tx, year).await?.into_iter().collect())
    }
    .await;
    match result {
        Ok(res) => GeoipResult::Ok(res),
        Err(err) => GeoipResult::Err(err),
    }
}

#[get("/geoip?<month>", rank = 3)]
async fn geoip_monthly(mut db: Connection<RecordDb>, _user: UserData, month: Month) -> GeoipResult {
    let result = async {
        let mut tx = db.begin().await?;
        Ok(get_month_geoip(&mut tx, month).await?.into_iter().collect())
    }
    .await;
    match result {
        Ok(res) => GeoipResult::Ok(res),
        Err(err) => GeoipResult::Err(err),
    }
}

#[get("/geoip?<week>", rank = 2)]
async fn geoip_weekly(mut db: Connection<RecordDb>, _user: UserData, week: Week) -> GeoipResult {
    let result = async {
        let mut tx = db.begin().await?;
        Ok(get_week_geoip(&mut tx, week).await?.into_iter().collect())
    }
    .await;
    match result {
        Ok(res) => GeoipResult::Ok(res),
        Err(err) => GeoipResult::Err(err),
    }
}

#[get("/geoip?<day>", rank = 1)]
async fn geoip_daily(mut db: Connection<RecordDb>, _user: UserData, day: Day) -> GeoipResult {
    let result = async {
        let mut tx = db.begin().await?;
        Ok(get_daily_geoip(&mut tx, day).await?.into_iter().collect())
    }
    .await;
    match result {
        Ok(res) => GeoipResult::Ok(res),
        Err(err) => GeoipResult::Err(err),
    }
}

#[cfg(debug_assertions)]
#[get("/geoip_test?<ip>")]
async fn geoip_test(
    handler: &rocket::State<crate::fairing::geoip::GeoIpHandler>,
    ip: std::net::IpAddr,
) -> (Status, Option<(rocket::http::ContentType, String)>) {
    use maxminddb::geoip2::City;
    use rocket::http::ContentType;
    let reader = match handler.reader() {
        Some(reader) => reader,
        None => return (Status::NotImplemented, None),
    };
    let city: City = match reader.lookup(ip) {
        Ok(city) => city,
        Err(_) => return (Status::NotFound, None),
    };
    let city = match serde_json::to_string(&city) {
        Ok(city) => city,
        Err(_) => return (Status::InternalServerError, None),
    };
    (Status::Ok, Some((ContentType::JSON, city)))
}

pub fn route() -> Vec<Route> {
    #[allow(unused_mut)]
    let mut routes = routes![geoip_yearly, geoip_monthly, geoip_weekly, geoip_daily];
    #[cfg(debug_assertions)]
    {
        routes.push(geoip_test {}.into_route());
    }
    routes
}
